package org.bendev.ildaplayer;

import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;

import android.net.Uri;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;

public abstract class SolrAdapter {
	private static final String TAG = "SolrAdapter";
	private static final Uri URI = Uri.parse( "http://161.105.180.14/solr/");
	private static final Pattern QR_D = Pattern.compile( "\\d+" );
	
	public static class Program implements Serializable{
		public static class Person implements Serializable{
			public final String name, role;
			public Person( String name, String role ){
				this.name = name;
				this.role = role;
			}
		}
		
		public final String epg_id, channel, name, description;
		public final Calendar start, stop;
		public Calendar preciseStart, preciseStop;
		public final List<Person> persons;
		public final int genre;
		public Program( String epg_id, String channel, String name, Calendar start, Calendar stop, String description, List<Person> persons, int genre ){
			this.epg_id = epg_id;
			this.channel = channel;
			this.name = name;
			this.start = start;
			this.stop = stop;
			this.description = description;
			this.persons = persons;
			this.genre = genre;
		}
	}
	
	public static interface OnChannelsListener{
		public void onChannels( List<String> channels );
	}
	
	public static interface OnProgramsListener{
		public void onPrograms( List<Program> programs );
	}
	
	public static interface OnProgramListener{
		public void onProgram( Program program );
	}
	
	public static GetChannelsTask getChannels( OnChannelsListener l ){
		return new GetChannelsTask( l );
	}
	
	public static GetProgramsTask getPrograms( String channel, Calendar minDate, Calendar maxDate, OnProgramsListener l ){
		return new GetProgramsTask( channel, minDate, maxDate, l );
	}

	public static GetProgramTask getProgram( Program p, OnProgramListener l ){
		return new GetProgramTask( p, l );
	}
	
	public static class GetProgramsTask extends AsyncTask<Void, Void, List<Program>>{
		final String channel;
		final Calendar minDate, maxDate;
		final OnProgramsListener listener;
		
		GetProgramsTask( String c, Calendar mnd, Calendar mxd, OnProgramsListener l ){
			channel = c;
			minDate = mnd;
			maxDate = mxd;
			listener = l;
			execute();
		}
		
		@Override
		protected List<Program> doInBackground( Void... params ) {
			String
				q = String.format( "type:EPG AND channel:%s AND start:[%s TO %s]", channel, 
						DateUtils.iso( DateUtils.add( minDate, Calendar.HOUR, -1 ) ), 
						DateUtils.iso( maxDate ) ),
				url = URI.buildUpon().appendPath( "select" )
					.appendQueryParameter( "q", q )
					.appendQueryParameter( "rows", "500" )
					.appendQueryParameter( "sort", "start asc" )
					.build().toString();
			return getPrograms( url );
		}
		
		@Override
		protected void onPostExecute( List<Program> result ) {
			if( listener!=null )
				listener.onPrograms( result );
		}
	}
	
	public static class GetChannelsTask extends AsyncTask<Void, Void, List<String>>{
		final OnChannelsListener listener;
		
		GetChannelsTask( OnChannelsListener l ){
			listener = l;
			execute();
		}
		
		@Override
		protected List<String> doInBackground( Void... params ) {
			String url = URI.buildUpon().appendPath( "select" )
				.appendQueryParameter( "q", "*:*" )
				.appendQueryParameter( "facet", "true" )
				.appendQueryParameter( "facet.field", "channel" )
				.appendQueryParameter( "rows", "0" )
				.appendQueryParameter( "omitHeader", "true" )
				.build().toString();
			final List<String> res = new ArrayList<String>();
			try{
				Log.d( TAG, String.format("getting %s", url));
				HttpURLConnection conn = (HttpURLConnection) new URL( url ).openConnection();
				conn.setConnectTimeout( 10000 );
				XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
				xpp.setInput( conn.getInputStream(), conn.getContentEncoding() );
				for( int e=xpp.getEventType(); e!=XmlPullParser.END_DOCUMENT; e=xpp.next() ){
					switch( e ){
					case XmlPullParser.START_TAG:
						if( xpp.getName().equals("int") )
							 res.add( xpp.getAttributeValue( null, "name" ) ); 
						break;
					}
				}
				Collections.sort( res, new Comparator<String>(){
					@Override
					public int compare( String lhs, String rhs ) {
						Matcher lm = QR_D.matcher( lhs ), rm = QR_D.matcher( rhs );
						if( !lm.find() ) return rm.find() ? 1 : lhs.compareTo( rhs );
						if( !rm.find() ) return -1;
						int li = Integer.parseInt( lm.group() ), ri = Integer.parseInt( rm.group() );
						return li-ri;
					}
				});
				Log.d( TAG, String.format("got %d channels", res.size() ) );
			}catch( Exception e){
				Log.e( TAG, "exception", e );
			}
			return res;
		}
		
		@Override
		protected void onPostExecute( List<String> result ){
			if( listener!=null )
				listener.onChannels( result );
		}
	}
	
	public static class GetProgramTask extends AsyncTask<Void, Void, Program>{
		final Program program;
		final OnProgramListener listener;

		GetProgramTask( Program p, OnProgramListener l ){
			program = p;
			listener = l;
			execute();
		}
		
		@Override
		protected Program doInBackground( Void... params ){
			String
				q = String.format( "type:Rule AND channel:%s AND start:[%s TO %s]", program.channel, 
						DateUtils.iso( DateUtils.add( program.start, Calendar.HOUR, -1 ) ), 
						DateUtils.iso( DateUtils.add( program.start, Calendar.HOUR, 1 ) ) ),
				url = URI.buildUpon().appendPath( "select" )
					.appendQueryParameter( "q", q )
					.build().toString();
			for( Program p: getPrograms( url ) )
				if( program.epg_id.equals( p.epg_id ) ){
					program.preciseStart = p.start;
					program.preciseStop = p.stop;
					break;
				}
			return program;
		}
		
		@Override
		protected void onPostExecute( Program result ){
			if( listener!=null )
				listener.onProgram( result );
		}
	}
	
	@SuppressWarnings( "unchecked" )
	static String value( Hashtable<String,Object> table, String key ){
		Object value = table.get( key );
		if( value==null )
			return "";
		return value instanceof String ? (String) value : TextUtils.join( "\n", (List<String>) value );
	}
	
	static List<Program.Person> persons( Hashtable<String,Object> table, String... roles ){
		List<Program.Person> res = new ArrayList<Program.Person>();
		for( String role: roles ){
			@SuppressWarnings( "unchecked" )
			List<String> tokens = (List<String>) table.get("epg_artist_"+role);
			if( tokens!=null && tokens.size()!=0 )
				for( String token: tokens )
					res.add( new Program.Person( token, role ) );
		}
		return res;
	}
	
	static List<Program> getPrograms( String url ){
		final List<Program> res = new ArrayList<Program>();
		try{
			Log.d(TAG, String.format("getting %s", url));
			long startTime = Calendar.getInstance().getTimeInMillis();
			long queryTime = 0;
			HttpURLConnection conn = (HttpURLConnection) new URL( url ).openConnection();
			conn.setConnectTimeout( 10000 );
			XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
			xpp.setInput( conn.getInputStream(), conn.getContentEncoding() );
			Hashtable<String, Object> fields = new Hashtable<String, Object>();
			long responseTime = Calendar.getInstance().getTimeInMillis();
			String key = null, text = null, tag, name;
			List<Object> values = null;
			for( int e=xpp.getEventType(); e!=XmlPullParser.END_DOCUMENT; e=xpp.next() ){
				switch( e ){
				case XmlPullParser.START_TAG:
					tag = xpp.getName();
					name = xpp.getAttributeValue( null, "name" );
					
					if( tag.equals("doc") )
						fields.clear();
					else if( (name!=null || key!=null) && ( tag.equals("str") || tag.equals("date") || tag.equals("int") ) ){
						if( key==null )
							key = name;
						text = "";
					}
					else if( name!=null && tag.equals("arr") ){
						key = name;
						values = new ArrayList<Object>();
					}
					else
						key = text = null;
					break;
				case XmlPullParser.END_TAG:
					tag = xpp.getName();
					if( tag.equals("doc") ){
						int epg_genre = 0;
						try{ epg_genre = Integer.parseInt( value( fields, "epg_genre" ).replaceFirst( "BFT-([0-9]+)-.*", "$1" ), 10 ); }catch( NumberFormatException ex ){}
						res.add( new Program( value( fields, "epg_id" ), value( fields, "channel" ), value( fields, "content" ), (Calendar) fields.get( "start" ), (Calendar) fields.get( "stop" ), 
								value( fields, "epg_long_summary" ), persons( fields, "AUT", "PRE", "ACT" ), epg_genre ) );
					}
					else if( key!=null ){
						if( tag.equals("arr") ){
							fields.put( key, values );
							values = null;
						}
						else{
							Object value = tag.equals("date") ? DateUtils.iso( text ) : tag.equals("int") ? Integer.parseInt( text ) : text ;
							if( key.equals( "QTime" ) )
								queryTime = (Integer) value;
							else if( values!=null )
								values.add( value );
							else{
								fields.put( key, value );
								key = null;
							}
						}
					}
					text = null;
					break;
				case XmlPullParser.TEXT:
					if( text!=null )
						text+= xpp.getText();
					break;
				}
			}
			Log.d( TAG, String.format("got %d programs in %d ms (resTime: %d, QTime: %d)", res.size(), Calendar.getInstance().getTimeInMillis()-startTime, Calendar.getInstance().getTimeInMillis()-responseTime, queryTime ) );
		}catch( Exception e){
			Log.e( TAG, "exception", e );
		}
		return res;
		
	}
}
